package chapter_09;

// 包和接口

/*
包和接口是Java程序的两个基本组成。一般来说，Java源程序可以包含下面的四个内部
部分的任何一个（或所有）。
· 单个接口声明（可选）
· 任意数目的引入语句（可选）
· 单个公共类声明（必须）
· 对包来说是私有的任意数目的类（可选）

1. 包
创建一个包是很简单的：只要包含一个package命令作为一个Java源文件的第一句就可以了。
该文件中定义的任何类将属于指定的包。package语句定义了一个存储类的名字空间。如果你
省略package语句，类名被输入一个默认的没有名称的包。
下面是package声明的通用形式：
package pkg;
目录名必须和包名严格匹配。
一个多级包的声明的通用形式如下：
package pkg1[.pkg2[.pkg3]];

2. 访问保护
Java将类成员的可见度分为四个种类：
· 相同包中的子类
· 相同包中的非子类
· 不同包中的子类
· 既不在相同包又不在相同子类中的类
三个访问控制符，private、public和protected，提供了多种方法来产生这些种类所需访问的多个级别：
                        Private成员 默认的成员 Protected成员 Public成员
同一类中可见 			    是 			是 			是 				是
同一个包中对子类可见 	    否 			是 			是 				是
同一个包中对非子类可见 	否 			是 			是 				是
不同包中对子类可见 		否 			否 			是 				是
不同的包中对非子类可见 	否 			否 			否 				是
Java的访问控制机制看上去很复杂，我们可以按下面方法简化它。任何声明为public的内容
可以被从任何地方访问。被声明成private的成员不能被该类外看到。如果一个成员不含有一
个明确的访问说明，它对于子类或该包中的其他类是可见的。这是默认访问。如果你希望一个
元素在当前包外可见，但仅仅是元素所在类的子类直接可见，把元素定义成protected。
上表仅适用于类成员。一个类只可能有两个访问级别：默认的或是公共的。如果一个类声明
成public，它可以被任何其他代码访问。如果该类默认访问控制符，它仅可以被相同包中的
其他代码访问。

3. 引入包
在Java源程序文件中，import语句紧接着package语句（如果package 语句存在），它存
在于任何类定义之前，下面是import声明的通用形式：
import pkg1[.pkg2].(classname|*);
这里，pkg1是顶层包名，pkg2是在外部包中的用逗点（.）隔离的下级包名。除非是文件系
统的限制，不存在对于包层次深度的实际限制。最后，你要么指定一个清楚的类名，要么指
定一个星号（*），该星号表明Java编译器应该引入整个包。下面的代码段显示了所用的两
种形式：
import java.util.Date;
import java.io.*;
警告：星号形式可能会增加编译时间——特别是在你引入多个大包时。因为这个原因，明确的
命名你想要用到的类而不是引入整个包是一个好的方法。然而，星号形式对运行时间性能和
类的大小绝对没有影响。如果在你用星号形式引用的两个不同包中存在具有相同类名的类，
编译器将保持沉默，除非你试图运用其中的一个。这种情况下，你会得到一个编译时错误并
且必须明确的命名指定包中的类。任何你用到类名的地方，你可以使用它的全名，全名包括
它所有的包层次。例如，下面的程序使用了一个引入语句：
import java.util.*;
class MyDate extends Date {
}
没有import 语句的例子如下：
class MyDate extends java.util.Date {
}
当一个包被引入，仅仅是该包中声明成public的项目可以在引入代码中对非子类可用。

4. 接口
用关键字interface，可以从类的实现中抽象一个类的接口。也就是说，用interface，你
可以指定一个类必须做什么，而不是规定它如何去做。接口在语句构成上与类相似，但是它
缺少实例变量，而且它们定义的方法是不含方法体的。接口是为支持运行时动态方法解决而
设计的。通常，为使一个方法可以在类间调用，两个类都必须出现在编译时间里，以便Java
编译器可以检查以确保方法特殊是兼容的。这个需求导致了一个静态的不可扩展的类环境。
在一个系统中不可避免会出现这种情况，函数在类层次中越堆越高以致该机制可以为越来越
多的子类可用。接口的设计避免了这个问题。它们把方法或方法系列的定义从类层次中分开。
因为接口是在和类不同的层次中，与类层次无关的类实现相同的接口是可行的。这是实现接
口的真正原因所在。
注意：接口增添了很多应用程序所需的功能。在一种语言例如C++中这些应用程序通常借助于
多重继承来完成。
access interface name {
    return-type method-name1(parameter-list);
    return-type method-name2(parameter-list);
    type final-varname1 = value;
    type final-varname2 = value;
    // ...
    return-type method-nameN(parameter-list);
    type final-varnameN = value;
}
这里，access要么是public，要么就没有用修饰符。当没有访问修饰符时，则是默认访
问范围，而接口是包中定义的惟一的可以用于其他成员的东西。当它声明为public时，则
接口可以被任何代码使用。name是接口名，它可以是任何合法的标识符。注意定义的方法
没有方法体。它们以参数列表后面的分号作为结束。它们本质上是抽象方法；在接口中指
定的方法没有默认的实现。每个包含接口的类必需实现所有的方法。
接口声明中可以声明变量。它们一般是final 和static型的，意思是它们的值不能通过
实现类而改变。它们还必须以常量值初始化。如果接口本身定义成public ，所有方法和
变量都是public的。
一旦接口被定义，一个或多个类可以实现该接口。为实现一个接口，在类定义中包括
implements 子句，然后创建接口定义的方法。一个包括implements 子句的类的一般形
式如下：
access class classname [extends superclass]
    [implements interface [,interface...]] {
    // class-body
}
这里，access要么是public的，要么是没有修饰符的。如果一个类实现多个接口，这些
接口被逗号分隔。如果一个类实现两个声明了同样方法的接口，那么相同的方法将被其中
任一个接口客户使用。实现接口的方法必须声明成public。而且，实现方法的类型必须严
格与接口定义中指定的类型相匹配。
注意：当实现一个接口方法时，它必须声明成public。
警告：因为Java中在运行时动态查询方法与通常的方法调用相比会有一个非常庞大的花费，
所以在对性能要求高的代码中不应该随意地使用接口。

局部实现：
如果一个类包含一个接口但是不完全实现接口定义的方法，那么该类必须定义成abstract型。
例如：
abstract class Incomplete implements Callback {
    int a, b;
    void show() {
        System.out.println(a + " " + b);
    }
    // ...
}
这里，类Incomplete没有实现callback( )方法，必须定义成抽象类。任何继承Incomplete
的类都必须实现callback()方法或者它自己定义成abstract类。

接口可以扩展
接口可以通过运用关键字extends被其他接口继承。语法与继承类是一样的。当一个类是实现
一个继承了另一个的接口时，它必须实现接口继承链表中定义的所有方法。

接口中的变量
你可以使用接口来引入多个类的共享常量，这样做只需要简单的声明包含变量初始化
想要的值的接口就可以了。如果你的一个类中包含那个接口（就是说当你实现了接口时），
所有的这些变量名都将作为常量看待。
这与在C/C++中用头文件来创建大量的 #defined 常
量或const 声明相似。如果接口不包含方法，那么任何包含这样接口的类实际并不实现什么。
这就像类在类名字空间引入这些常量作final变量。
 */

import chapter_09.p2.*;


class Derived extends Protection {
    // 9.2
    Derived() {
        System.out.println("derived constructor");
        System.out.println("n = " + n);
        // class only
        // System.out.println("n_pri = " + n_pri);
        System.out.println("n_pro = " + n_pro);
        System.out.println("n_pub = " + n_pub);
    }
}


interface Callback {
    // 9.4
    void callback(int param);
    final int MAX_NUM = 10;		// 禁止继承接口的类修改它
    static int MIN_NUM = 1;		// 禁止继承接口的类修改它
}


class Client implements Callback {
    // 9.4
    // 实现一个接口方法时，它必须声明成public
    public void callback(int p) {
        System.out.println("callback called with " + p);
        //
        System.out.println("final成员 " + MAX_NUM);
        System.out.println("static成员 " + MIN_NUM);
    }

    // 类在实现接口时定义它自己的附加成员，既是允许的，也是常见的。
    void nonIfaceMeth() {
        System.out.println("XXXXXX");
    }
}


public class chapter_09 {

    static void test1() {
        // 9.2
        Protection p = new Protection();
        System.out.println("same package constructor");
        System.out.println("n = " + p.n);
        // class only
        // System.out.println("n_pri = " + p.n_pri);
        System.out.println("n_pro = " + p.n_pro);
        System.out.println("n_pub = " + p.n_pub);
    }

    static void test2() {
        // 9.2
        Derived a = new Derived();
    }

    static void test3() {
        // 9.2
        Protection2 a = new Protection2();
    }

    static void test4() {
        // 9.2
        OtherPackage a = new OtherPackage();
    }

    static void test5() {
        // 9.4
        /*
		 * 你可以把变量定义成使用接口的对象而不是类的类型。任何实现了所声明接口的类
		 * 的实例都可以被这样一个变量引用。当你通过这些引用调用方法时，在实际引用接口
		 * 的实例的基础上，方法被正确调用。这是接口的最显著特性之一。被执行的方法在
		 * 运行时动态操作，允许在调用方法代码后创建类。调用代码在完全不知“调用者”的
		 * 情况下可以通过接口来调度。
		 */
        Callback c = new Client();
        c.callback(10);
		/*
		 * 注意变量c被定义成接口类型Callback，而且被一个Client实例赋值。尽管c可以用来
		 * 访问Callback()方法，它不能访问Callback类中的任何其他成员。一个接口引用变量
		 * 仅仅知道被它的接口定义声明的方法。因此，c不能用来访问nonIfaceMeth()，因为
		 * 它是被Callback定义的，而不是由Callback定义。
		 */
    }

    static void test6() {
        Callback c = new Client();
        AnotherClient ob = new AnotherClient();

        // 多态
        c.callback(42);
        c = ob;	// c now refers to AnotherClient object
        c.callback(42);
    }

    public static void main(String[] args) {
//        test1();
//        test2();
//        test3();
//        test4();
//        test5();
        test6();
    }
}
